package cmm.Seman;

import cmm.Exception.SemanticException;
import cmm.Lexer.TokenType;
import cmm.Parser.NodeType;
import cmm.Parser.Parser;
import cmm.Parser.TreeNode;


import java.util.LinkedList;

import static cmm.Lexer.TokenType.*;

/**
 * Created by Iray on 2017/12/3.
 */
public class Seman {

    private static int mLevel;
    private static int mLine;

    private static LinkedList<FourCode> codes;
    private static SymbolTable symbolTable;

    public  LinkedList<FourCode> generateCode(Parser parser) throws Exception {
        mLine = -1;
        mLevel = 0;
        codes = new LinkedList<FourCode>();

        LinkedList<TreeNode> nodeList = parser.getTreeNodes();
        symbolTable = SymbolTable.getSymbolTable();

        System.out.println();
//        System.out.println("Start Seman");
        for (TreeNode node : nodeList) {
            stmt(node);
//            System.out.println(node.getType());
        }
        symbolTable.deleteTable();

        return codes;
    }

    private void stmt(TreeNode node) throws Exception {
//        while(true) {
            switch (node.getType()) {
                case IF_STMT:
                    ifStmt(node);
                    break;
                case WHILE_STMT:
                    whileStmt(node);
                    break;
                case READ_STMT:
                    readStmt(node);
                    break;
                case WRITE_STMT:
                    writeStmt(node);
                    break;
                case DECLARE_STMT:
                    declareStmt(node);
                    break;
                case FOR_STMT:
                    forStmt(node);
                    break;
                case ASSIGN_STMT:
                    assignStmt(node);
                    break;

                case NULL:
                {
                    for(TreeNode tr : node.getChildnodes()) {
                        stmt(tr);
//                        System.out.println("------------tr : " + tr.getType());
                    }
                    break;
                }

                default:
                    throw new SemanticException("未知符号");




//                case FUNCTION_CALL:
//                case FUNCTION_DECL:
//                case FUNCTION_SIGN:
//                case RETURN_STMT:
//                case PARALIST:

//            }
        }
    }


    private void ifStmt(TreeNode node) throws Exception {
        if(node.getType() != NodeType.IF_STMT)
            throw new SemanticException("未知 if 语句");

        //条件为false转跳, 后面会回填
        FourCode falsejmp = new FourCode(FourCode.JUMP, exp(node.getChildnodes().getFirst()), null, null);
        codes.add(falsejmp);
        mLine++;
        codes.add(new FourCode(FourCode.IN, null, null, null));
        mLine++;
        mLevel++;
        stmt(node.getChildnodes().get(1));
        mLevel--;
        codes.add(new FourCode(FourCode.OUT, null, null, null));
        mLine++;
        if (node.getChildnodes().size()>2) {
            FourCode outjump = new FourCode(FourCode.JUMP, null, null, null);
            codes.add(outjump);
            mLine++;
            //回填
            falsejmp.setForth(String.valueOf(mLine + 1));
            codes.add(new FourCode(FourCode.IN, null, null, null));
            mLine++;
            mLevel++;
            stmt(node.getChildnodes().get(2));
            codes.add(new FourCode(FourCode.OUT, null, null, null));
            mLine++;
            symbolTable.unregiste(mLevel);
            mLevel--;
            outjump.setForth(String.valueOf(mLine + 1));
        } else {
            //回填
            falsejmp.setForth(String.valueOf(mLine + 1));
        }
    }

    private void whileStmt(TreeNode node) throws Exception {
        FourCode jmp = new FourCode(FourCode.JUMP, exp(node.getChildnodes().getFirst()), null, null);
        int jmpline = mLine + 1;
        codes.add(jmp);
        mLine++;
        codes.add(new FourCode(FourCode.IN, null, null, null));
        mLine++;
        mLevel++;
        stmt(node.getChildnodes().get(1));
        symbolTable.unregiste(mLevel);
        mLevel--;
        codes.add(new FourCode(FourCode.OUT, null, null, null));
        mLine++;
        codes.add(new FourCode(FourCode.JUMP, null, null, jmpline -1 + ""));
        mLine++;
        jmp.setForth(String.valueOf(mLine +1));
    }

    private void readStmt(TreeNode node) throws Exception {

        int type = symbolTable.getType(node.getChildnodes().getFirst().getValue());
        switch (type) {
            case Symbol.INT:
            case Symbol.REAL:
                codes.add(new FourCode(FourCode.READ, null, null, node.getChildnodes().getFirst().getValue()));
                mLine++;
                break;
            case Symbol.INTL:
            case Symbol.REALL:
                codes.add(new FourCode(FourCode.READ, null, null, node.getChildnodes().getFirst().getValue() + "[" + exp(node.getChildnodes().getFirst().getChildnodes().getFirst()) + "]"));
                mLine++;
                break;

            default:
                throw new SemanticException("read 输入语句有误");
        }

    }

    private void writeStmt(TreeNode node) throws Exception {
        codes.add(new FourCode(FourCode.WRITE, null, null, exp(node.getChildnodes().get(1))));
        mLine++;

    }

    private void declareStmt(TreeNode node) throws Exception {

        TreeNode var = node.getChildnodes().getFirst();
        if (var.getChildnodes().size() < 1 || var.getChildnodes().getFirst() == null) {// 非数组
            String value = null;
            if (node.getChildnodes().get(1) != null && node.getChildnodes().get(1).getChildnodes().size() > 1 ) {
                value = exp(node.getChildnodes().get(1).getChildnodes().get(1));
            }
            if (var.getDataType() == INT) {
                var = node.getChildnodes().get(1);
                codes.add(new FourCode(FourCode.INT, value, null, var.getValue()));
                mLine++;
                Symbol symbol = new Symbol(var.getValue(), Symbol.INT, mLevel);
                symbolTable.registe(symbol);
            } else if (var.getDataType() == REAL) {
                var = node.getChildnodes().get(1);
                codes.add(new FourCode(FourCode.REAL, value, null, var.getValue()));
                mLine++;
                Symbol symbol = new Symbol(var.getValue(), Symbol.REAL, mLevel);
                symbolTable.registe(symbol);
            }
        } else {
            String len = exp(var.getChildnodes().getFirst());
//            String len = exp(node.getChildnodes().get(1).getChildnodes().getFirst());
            if (var.getDataType() == INT) {
//                codes.add(new FourCode(FourCode.INT, null, len, var.getValue()));
                codes.add(new FourCode(FourCode.INT, null, len, node.getChildnodes().get(1).getValue()));
                mLine++;
//                Symbol symbol = new Symbol(var.getValue(), Symbol.INT, mLevel);
                Symbol symbol = new Symbol( node.getChildnodes().get(1).getValue(), Symbol.INTL, mLevel, Integer.parseInt(len));
                symbolTable.registe(symbol);
            } else {
//                codes.add(new FourCode(FourCode.REAL, null, len, var.getValue()));
                codes.add(new FourCode(FourCode.REAL, null, len, node.getChildnodes().get(1).getValue()));
                mLine++;
//                Symbol symbol = new Symbol(var.getValue(), Symbol.REAL, mLevel);
                Symbol symbol = new Symbol(node.getChildnodes().get(1).getValue(), Symbol.REALL, mLevel, Integer.parseInt(len));
                symbolTable.registe(symbol);
            }
        }

    }

    private void forStmt(TreeNode node) throws Exception {


    }

    private void assignStmt(TreeNode node) throws Exception {
        String value = exp(node.getChildnodes().get(2));

        TreeNode var = node.getChildnodes().getFirst();
        if ( var.getChildnodes().size() < 1) {//单值
            codes.add(new FourCode(FourCode.ASSIGN, value, null, var.getValue()));
            mLine++;
        } else {
            String index = exp(var.getChildnodes().getFirst());
            if(index.matches("-*[0-9]+")) {
                if( Integer.parseInt(index) < 0) {
                    throw new SemanticException("index不能为负");

                }
            }
            int p = Integer.parseInt("9");
//            System.out.println(p);
            codes.add(new FourCode(FourCode.ASSIGN, value, null, var.getValue() + "[" + index + "]"));
            mLine++;
        }


    }


    private String exp(TreeNode node) throws Exception {
        if (node.getType() == NodeType.EXP) {
            switch (node.getDataType()) {
                case LOGIC_EXP:
                    return logicExp(node);
                case ADDTIVE_EXP:
                    return addtiveExp(node);
                case TERM_EXP:
                    return termExp(node);
                default:
                    throw new SemanticException("复合表达式非法");
            }
        } else if (node.getType() == NodeType.FACTOR) {
            if (node.getDataType() == MINUS) {
                String temp = symbolTable.getTempSymbol().getName();
                codes.add(new FourCode(FourCode.MINUS, exp(node.getChildnodes().getFirst()), null, temp));
                mLine++;
                return temp;
            } else {
                return exp(node.getChildnodes().getFirst());
            }
        } else if (node.getType() == NodeType.VAR) {
            if (node.getChildnodes().size() < 1 || node.getChildnodes().getFirst() == null) {//单值
                if (symbolTable.getType(node.getValue()) == Symbol.INT || symbolTable.getType(node.getValue()) == Symbol.REAL) {
                    return node.getValue();
                }
            } else {
                if (symbolTable.getType(node.getValue()) == Symbol.INTL || symbolTable.getType(node.getValue()) == Symbol.REALL) {
                    String temp = symbolTable.getTempSymbol().getName();
                    String index = exp(node.getChildnodes().getFirst());
                    codes.add(new FourCode(FourCode.ASSIGN, node.getValue() + "[" + index + "]", null, temp));
                    mLine++;
                    return temp;
                }
            }
        } else if (node.getType() == NodeType.LITREAL) {
            return node.getValue();
        }
        throw new SemanticException("表达式非法: " +  node.getValue() + ", " + node.getType());

    }

    private String logicExp(TreeNode node) throws Exception {
        String temp = symbolTable.getTempSymbol().getName();
        if(node.getChildnodes().size() < 3)
            throw new SemanticException("logicExp 逻辑比较参数不够");
        switch ( node.getChildnodes().get(1).getDataType()) {
            case GT:
                codes.add(new FourCode(FourCode.GT, exp(node.getChildnodes().getFirst()), exp(node.getChildnodes().get(2)), temp));
                break;
            case GET:
                codes.add(new FourCode(FourCode.GET, exp(node.getChildnodes().getFirst()), exp(node.getChildnodes().get(2)), temp));
                break;
            case LT:
                codes.add(new FourCode(FourCode.LT, exp(node.getChildnodes().getFirst()), exp(node.getChildnodes().get(2)), temp));
                break;
            case LET:
                codes.add(new FourCode(FourCode.LET, exp(node.getChildnodes().getFirst()), exp(node.getChildnodes().get(2)), temp));
                break;
            case EQ:
                codes.add(new FourCode(FourCode.EQ, exp(node.getChildnodes().getFirst()), exp(node.getChildnodes().get(2)), temp));
                break;
            case NEQ:
                codes.add(new FourCode(FourCode.NEQ, exp(node.getChildnodes().getFirst()), exp(node.getChildnodes().get(2)), temp));
                break;
            default:
                throw new SemanticException("logicExp逻辑比较非法");
        }
        mLine++;
        return temp;
    }

    private String addtiveExp(TreeNode node) throws Exception {
        String temp = symbolTable.getTempSymbol().getName();
        if(node.getChildnodes().size() < 3)
            throw new SemanticException("算术运算参数不够");
        switch (node.getChildnodes().get(1).getDataType()) {
            case PLUS:
                codes.add(new FourCode(FourCode.PLUS, exp(node.getChildnodes().get(0)), exp(node.getChildnodes().get(2)), temp));

                break;
            case MINUS:
                codes.add(new FourCode(FourCode.MINUS, exp(node.getChildnodes().get(0)), exp(node.getChildnodes().get(2)), temp));

                break;
            default:
                throw new SemanticException("算数运算非法");
        }
        mLine++;
        return temp;

    }

    private String termExp(TreeNode node) throws Exception {
        String opcode = getOpcode(node.getChildnodes().get(1).getDataType());
        String temp1 = symbolTable.getTempSymbol().getName();
        if (node.getChildnodes().get(2).getType() == NodeType.FACTOR || node.getChildnodes().get(2).getType() == NodeType.VAR) {
            codes.add(new FourCode(opcode, exp(node.getChildnodes().getFirst()), exp(node.getChildnodes().get(2)), temp1));
            mLine++;
        } else {
            codes.add(new FourCode(opcode, exp(node.getChildnodes().getFirst()), exp(node.getChildnodes().get(2).getChildnodes().getFirst()), temp1));
            mLine++;
            node = node.getChildnodes().get(2);
            String temp2 = null;
            while (node.getChildnodes().size() >1 && node.getChildnodes().get(0).getType() != NodeType.FACTOR) {
                opcode = getOpcode(node.getChildnodes().get(1).getDataType());
                temp2 = symbolTable.getTempSymbol().getName();
                codes.add(new FourCode(opcode, temp1, exp(node.getChildnodes().get(2).getChildnodes().getFirst()), temp2));
                mLine++;
                node = node.getChildnodes().get(2);
                temp1 = temp2;
            }
            if (node.getChildnodes().size() > 1)
                opcode = getOpcode(node.getChildnodes().get(1).getDataType());
            else {
                opcode = getOpcode(node.getChildnodes().get(0).getDataType());
                return temp1;
            }
            temp2 = symbolTable.getTempSymbol().getName();
            codes.add(new FourCode(opcode, temp1, exp(node.getChildnodes().get(2)), temp2));
            mLine++;
            temp1 = temp2;
        }
        return temp1;

    }

    private String getOpcode(TokenType op) {
        if (op == TokenType.MUL) {
            return FourCode.MUL;
        } else  if (op == TokenType.PLUS) {
            return FourCode.PLUS;
        } else  if (op == TokenType.MINUS) {
            return FourCode.MINUS;
        } else {//Token.DIV
            return FourCode.DIV;
        }
    }

    public static LinkedList<FourCode> getCodes() {
        return codes;
    }

}
